#include "game_socket.h"
#include "game_macros.h"
#include "game_version.h"
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include "game_ev.h"

CGameSocketd::Connection::Connection(CGameSocketd *socketd) : _socketd(socketd)
{
}

CGameSocketd::Connection::~Connection()
{
  if(!_bufferevent){
    // 这将自动close套接字和free读写缓冲区
    bufferevent_free(_bufferevent);
  }

  if(!_remoteAddress){
    delete _remoteAddress;
  }
}
int CGameSocketd::Connection::send(const void *ptr, int len)
{
  int ret = 0;
   do{
    CC_BREAK_IF(0 >= (ret = bufferevent_write(_bufferevent,ptr,len)));
    return ret;
  }while(true);


  __warn("Error when flush: [%d]%s\n", errno, strerror(errno));
  return ret;
}

void CGameSocketd::Connection::onError(){
}

void CGameSocketd::Connection::onClose(){
  printf("connection closed\n");
}

void CGameSocketd::Connection::onRead(bufferevent *bev)
{
  char msg[4096];

  size_t len = bufferevent_read(bev, msg, sizeof(msg));

  msg[len] = '\0';
  printf("recv the client msg: %s\n", msg);

  // char reply_msg[4096] = "I have recvieced the msg: ";

  // strcat(reply_msg + strlen(reply_msg), msg);
  // bufferevent_write(bev, reply_msg, strlen(reply_msg));
}

BOOL CGameSocketd::Connection::init(sockfd_t fd, event_base* evbase, const sockaddr_in &sa)
{
  _remoteAddress = inet_ntoa(sa.sin_addr);
  _remotePort = sa.sin_port;
  _sockfd = fd;
  _bufferevent = bufferevent_socket_new(evbase, fd, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_THREADSAFE);
  bufferevent_setcb(_bufferevent, CGame::Socket::__socketd_read_cb<CGameSocketd::Connection, &CGameSocketd::Connection::onRead>, NULL, CGame::Socket::__socketd_events_cb<CGameSocketd::Connection, &CGameSocketd::Connection::onEvents>, this);
  bufferevent_enable(_bufferevent, EV_READ | EV_PERSIST);
  return TRUE;
}

void CGameSocketd::Connection::onEvents(bufferevent *bev, short events)
{
  if (events & BEV_EVENT_EOF){
    onClose();
  }else if (events & BEV_EVENT_ERROR)
    printf("some other error\n");


}

void CGameSocketd::Connection::close(){
  ::close(_sockfd);
}

void CGameSocketd::onAccept(Connection *conn, int fd, short events, void *arg)
{
  sockaddr_in sa;
  socklen_t len = sizeof(sa);

  sockfd_t sockfd = ::accept(fd, (struct sockaddr *)&sa, &len);

  conn->init(sockfd, _evbase,sa);
}

void CGameSocketd::onAccept(int fd, short events, void *arg)
{

  Connection *inconn = new Connection(this);
  onAccept(inconn, fd, events, arg);
}

BOOL CGameSocketd::init(int port)
{
  return init("0.0.0.0", port);
}
BOOL CGameSocketd::init(const char *addr, int port)
{
  return init(g_CGameEV.getBase(),addr, port);
}

sockfd_t CGameSocketd::socket()
{
#if CGAME_TARGET_PLATFORM == CGAME_PLATFORM_MAC
  _fd = ::socket(AF_INET, SOCK_STREAM, 0);
  fcntl(_fd, F_SETFL, O_NONBLOCK);
#elif CGAME_TARGET_PLATFORM == CGAME_PLATFORM_LINUX
  int opt = 1;
  _fd = ::socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0); // NOTE 多线程evhttp必须非阻塞
  setsockopt(_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
#endif
  return _fd;
}



BOOL CGameSocketd::init(event_base* evbase,const char *addr, int port){
  int ret = -1;
  do
  {
    CC_BREAK_IF(0 >= (_fd = socket()));
    // 允许多次绑定同一个地址。要用在socket和bind之间
    evutil_make_listen_socket_reuseable(_fd);

    CC_BREAK_IF(!CGameSocketd::listen(_fd, addr, port));
    // 跨平台统一接口，将套接字设置为非阻塞状态
    evutil_make_socket_nonblocking(_fd);

    const int flags = LEV_OPT_REUSEABLE | LEV_OPT_CLOSE_ON_EXEC | LEV_OPT_CLOSE_ON_FREE | LEV_OPT_THREADSAFE;
    // for(int i = 0 ;i < worker;i++){
    // SocketdContext_t *ctx = new SocketdContext_t();

    _evbase = evbase;

    // 添加监听客户端请求连接事件
    _ev = event_new(_evbase, _fd, EV_READ | EV_PERSIST, CGame::Socket::__socketd_accept_cb<CGameSocketd, &CGameSocketd::onAccept>, this);
    event_add(_ev, NULL);

    char v[64] = {0};
    __version(v);
    __log("Init %s/%s on port: %d.\n", getName(), v, port);



    return TRUE;

  } while (true);

  return ret;
}


const char *CGameSocketd::getName()
{
  return "CGameSocketd";
}

BOOL CGameSocketd::listen(sockfd_t fd, const char *addr, int port)
{

  do
  {
    int ret = 0;
    struct sockaddr_in addr_in;
    memset(&addr_in, 0, sizeof(addr_in));
    addr_in.sin_family = AF_INET;
    addr_in.sin_addr.s_addr = inet_addr(addr);
    addr_in.sin_port = htons(port);
    CC_BREAK_IF_LOG(0 > (ret = ::bind(_fd, (struct sockaddr *)&addr_in, sizeof(struct sockaddr))), "Error when bind: [%d]%s\n", errno, strerror(errno));

    CC_BREAK_IF_LOG(0 > (ret = ::listen(_fd, 1024)), "Error when listen: [%d]%s\n", errno, strerror(errno));

    return TRUE;

  } while (true);

  return FALSE;
}

void CGameSocketd::run()
{
  event_base_loop(_evbase, EVLOOP_NONBLOCK);
}

CGameSocketd::CGameSocketd()
{
}

CGameSocketd::~CGameSocketd()
{
}
